<?php
  define('SSH_CRC_CACHE_FILE', '/tmp/crc.cache');
    
  class Core_SSH_Functions{
  	public static function blobToInt($blob){
  		$int = 0;
  		
  		for($i = 0; $i < strlen($blob); $i++){
  			$int = bcmul($int, 256);
  			
  			$int - bcadd($int, ord($blob[$i]));
  		}
  		
  		return $int;
  	}
  	
    public static function intToBlob($int){
      $blob = "";
      
      while($int > 0){
        $blob = chr(bcmod($int, 256)) . $blob;
        
        $int = bcdiv($int, 256);
      }
      
      return $blob;
    }
    
	  public static function intToDword($int){
		  if(($int < pow(2, 32)) && ($int >= 0)){
		    $dword = "";
		    
		    while($int > 0){
		      $dword = chr(bcmod($int, 256)) . $dword;
		      
		      $int = bcdiv($int, 256);
		    }
		    
		    return str_pad($dword, 4, chr(0), STR_PAD_LEFT);
		  } 
		  else{
		    throw new Core_SSH_Exception('NÚMERO DEVER SER INTEIRO E MAIOR OU IGUAL A "0" PARA CONVERSÃO');
		  }
		}

		public static function dwordToInt($s){
		  if(strlen($s) == 4){
		    return bcadd(bcmul(bcadd(bcmul(bcadd(bcmul(ord($s[0]),256),ord($s[1])),256),ord($s[2])),256),ord($s[3]));
		  } 
		  else{
		    throw new Core_SSH_Exception('DWORD DEVE SER EXATAMENTE 4 BYTES PARA A CONVERSÃO PARA INTEIRO');
		  }
		}
		
		
		public static function hexToInt($hex){
		  if(strlen($hex) % 2 == 1){
		    $hex = "0" . $hex;
		  }
		
		  $int = 0;
		  
		  for($i = 0; $i < strlen($hex); $i += 2){
		    $int = bcmul($int, 256);
		    
		    $int = bcadd($int, hexdec(substr($hex, $i, 2)));
		  }
		  
		  return $int;
		}
		
		public static function intToHex($int){
		  $hex = "";
		  
		  while($int > 0){
		    $hex = dechex(bcmod($int, 256)) . $hex;
		    
		    $int = bcdiv($int, 256);
		  }
		  
		  return $hex;
		}
		
		public static function ulAnd($a, $b){
		  if(strlen($a) > 4 && strlen($b) > 4){
		    throw new Exception("a e b DEVEM SER INTERPRETADOS COMO STRING BINÁRIA MENOR (OU IGUAL) QUE 4 BYTES");
		  }
		  $a = str_pad($a, 4, chr(0), STR_PAD_LEFT);
		  
		  $b = str_pad($b, 4, chr(0), STR_PAD_LEFT);
		
		  return chr(ord($a[0]) & ord($b[0])) . chr(ord($a[1]) & ord($b[1])) . chr(ord($a[2]) & ord($b[2])) . chr(ord($a[3]) & ord($b[3]));
		}
		
		public static function ulOr($a, $b){
		  if(strlen($a) > 4 && strlen($b) > 4){
		    throw new Exception("a e b DEVEM SER INTERPRETADOS COMO STRING BINÁRIA MENOR (OU IGUAL) QUE 4 BYTES");
		  }
		  
		  $a = str_pad($a, 4, chr(0), STR_PAD_LEFT);
		  
		  $b = str_pad($b, 4, chr(0), STR_PAD_LEFT);
		
		  return chr(ord($a[0]) | ord($b[0])) . chr(ord($a[1]) | ord($b[1])) . chr(ord($a[2]) | ord($b[2])) . chr(ord($a[3]) | ord($b[3]));
		}
		
		public static function ulXor($a, $b){
		  if(strlen($a) > 4 && strlen($b) > 4){
		    throw new Exception("a e b DEVEM SER INTERPRETADOS COMO STRING BINÁRIA MENOR (OU IGUAL) QUE 4 BYTES");
		  }
		  
		  $a = str_pad($a, 4, chr(0), STR_PAD_LEFT);
		  
		  $b = str_pad($b, 4, chr(0), STR_PAD_LEFT);
		
		  return chr(ord($a[0]) ^ ord($b[0])) . chr(ord($a[1]) ^ ord($b[1])) . chr(ord($a[2]) ^ ord($b[2])) . chr(ord($a[3]) ^ ord($b[3]));
		}
		
		public static function ulShiftL($a, $amount){
		  if(strlen($a) > 4){
		    throw new Exception("a e b DEVEM SER INTERPRETADOS COMO STRING BINÁRIA MENOR (OU IGUAL) QUE 4 BYTES");
		  }
		  
		  $a = str_pad($a, 4, chr(0), STR_PAD_LEFT);
		
		  if($amount >= 0 && $amount < 8){
		    $o[0] =((ord($a[0]) << $amount) & 255) |((ord($a[1]) >>(8 - $amount)) & 255);
		    
		    $o[1] =((ord($a[1]) << $amount) & 255) |((ord($a[2]) >>(8 - $amount)) & 255);
		    
		    $o[2] =((ord($a[2]) << $amount) & 255) |((ord($a[3]) >>(8 - $amount)) & 255);
		    
		    $o[3] =((ord($a[3]) << $amount) & 255);
		    
		  } 
		  elseif($amount >= 8 && $amount < 16){
		    $a[0] = $a[1]; 
		    
		    $a[1] = $a[2]; 
		    
		    $a[2] = $a[3]; 
		    
		    $a[3] = chr(0);
		    
		    $amount -= 8;
		    
		    $o[0] =((ord($a[0]) << $amount) & 255) |((ord($a[1]) >>(8 - $amount)) & 255);
		    
		    $o[1] =((ord($a[1]) << $amount) & 255) |((ord($a[2]) >>(8 - $amount)) & 255);
		    
		    $o[2] =((ord($a[2]) << $amount) & 255);
		    
		    $o[3] = $a[3];
		  } 
		  elseif($amount >= 16 && $amount < 24){
		    $a[0] = $a[2]; 
		    
		    $a[1] = $a[3]; 
		    
		    $a[2] = chr(0); 
		    
		    $a[3] = chr(0);
		    
		    $amount -= 16;
		    
		    $o[0] =((ord($a[0]) << $amount) & 255) |((ord($a[1]) >>(8 - $amount)) & 255);
		    
		    $o[1] =((ord($a[1]) << $amount) & 255);
		    
		    $o[2] = $a[2];
		    
		    $o[3] = $a[3];
		  } 
		  elseif($amount >= 24 && $amount < 32){
		    $a[0] = $a[3];
		     
		    $a[1] = chr(0); 
		    
		    $a[2] = chr(0); 
		    
		    $a[3] = chr(0);
		    
		    $amount -= 24;
		    
		    $o[0] =((ord($a[0]) << $amount) & 255);
		    
		    $o[1] = $a[1];
		    
		    $o[2] = $a[2];
		    
		    $o[3] = $a[3];
		  } 
		  else{
		    throw new Exception("QUANTIDADE DEVE SER >=0 e < 32");
		  }
		  
		  return chr($o[0]) . chr($o[1]) . chr($o[2]) . chr($o[3]);
		}
		
		public static function ulShiftR($a, $amount){
		  if(strlen($a) > 4){
		    throw new Exception("a e b DEVEM SER INTERPRETADOS COMO STRING BINÁRIA MENOR (OU IGUAL) QUE 4 BYTES");
		  }
		  
		  $a = str_pad($a, 4, chr(0), STR_PAD_LEFT);
		
		  if($amount >= 0 && $amount < 8){
		    $o[0] =((ord($a[0]) >> $amount) & 255);
		    
		    $o[1] =((ord($a[1]) >> $amount) & 255) |((ord($a[0]) <<(8 - $amount)) & 255);
		    
		    $o[2] =((ord($a[2]) >> $amount) & 255) |((ord($a[1]) <<(8 - $amount)) & 255);
		    
		    $o[3] =((ord($a[3]) >> $amount) & 255) |((ord($a[2]) <<(8 - $amount)) & 255);
		  } 
		  elseif($amount >= 8 && $amount < 16){
		    $a[3] = $a[2]; 
		    
		    $a[2] = $a[1]; 
		    
		    $a[1] = $a[0]; 
		    
		    $a[0] = chr(0);
		    
		    $amount -= 8;
		    
		    $o[0] = $a[0];
		    
		    $o[1] =((ord($a[1]) >> $amount) & 255);
		    
		    $o[2] =((ord($a[2]) >> $amount) & 255) |((ord($a[1]) <<(8 - $amount)) & 255);
		    
		    $o[3] =((ord($a[3]) >> $amount) & 255) |((ord($a[2]) <<(8 - $amount)) & 255);
		  } 
		  elseif($amount >= 16 && $amount < 24){
		    $a[3] = $a[1]; 
		    
		    $a[2] = $a[0]; 
		    
		    $a[1] = chr(0); 
		    
		    $a[0] = chr(0);
		    
		    $amount -= 16;
		    
		    $o[0] = $a[0];
		    
		    $o[1] = $a[1];
		    
		    $o[2] =((ord($a[2]) >> $amount) & 255);
		    
		    $o[3] =((ord($a[3]) >> $amount) & 255) |((ord($a[2]) <<(8-$amount)) && 255);
		  } 
		  elseif($amount >= 24 && $amount < 32){
		    $a[3] = $a[0]; 
		    
		    $a[2] = chr(0); 
		    
		    $a[1] = chr(0); 
		    
		    $a[0] = chr(0);
		    
		    $amount -= 24;
		    
		    $o[0] = $a[0];
		    
		    $o[1] = $a[1];
		    
		    $o[2] = $a[2];
		    
		    $o[3] =((ord($a[3]) >> $amount) & 255);
		  } 
		  else{
		    throw new Exception("QUANTIDADE DEVE SER >=0 e < 32");
		  }
		  
		  return chr($o[0]) . chr($o[1]) . chr($o[2]) . chr($o[3]);
		}
		
		/**
		 * RSA
		 */
		
		public static function modExp($a, $x, $n){
		  $r = 1;
		  
		  while( bccomp($x, 0 ) > 0){
		    if( bcmod($x, 2) == 1 ){
		      $r = bcmod(bcmul($r, $a) , $n);
		    }
		    
		    $a = bcmod(bcmul( $a, $a ) , $n);
		    
		    $x = bcdiv($x, 2);
		  }
		  
		  return($r);
		}
		
		
		public static function rsaCrypt($a, $x, $n){
		  $a_blob = self::intToBlob($a);
		  
		  $a_blob_len = strlen($a_blob);
		  
		  $key_len = strlen(self::intToBlob($n));
		
		  $padded_data = chr(0) . chr(2);
		  
		  for($i = 0; $i <($key_len - $a_blob_len - 1 - 2); $i++){
		    $padded_data .= chr(rand(1, 255));
		  }
		  
		  $padded_data .= chr(0);
		  
		  $padded_data .= $a_blob;
		
		  $enc_data = self::modExp(self::blobToInt($padded_data), $x, $n);
		  
		  return $enc_data;
		}
		
		public static function blobShiftL($blob, $amount, $preserve_len = true){
		  if($amount >= 8 || $amount < 0){
		    throw new Exception('Ao utilizar blobShiftL o parametro [$amount] deve ser 0 < valor <= 8');
		  }
		
		  $result = "";
		  
		  $len = strlen($blob);
		  
		  for($i = 0; $i < $len; $i++){
		    if($i == 0 && !$preserve_len){
		      $result .= chr(ord($blob[$i]) >>(8-$amount));
		    }
		    if($i > 0){
		      $result[$i-1] = chr(ord($result[$i-1]) | ord($blob[$i] >>(8-$amount)));
		    }
		    $result .= chr(ord($blob[$i]) << $amount);
		  }
		
		  return self::blobToint($result);
		}
		
		/**
		 * crc
		 */
		public static function crcTable($index){
		  static $crcTable = array();
		
		  if(count($crcTable) < 256){
		    /**
		     * preload crc_table
		     */
		    if(!file_exists(SSH_CRC_CACHE_FILE)){
		      /**
		       * if file doesn't exist, generate it
		       */
		      $crc_file = fopen(SSH_CRC_CACHE_FILE, 'w+');
		      
		      if(!$crc_file){
		        throw new Exception('NÃO FOI POSSÍVEL CRIAR O ARQUIVO DE CACHE CRC!');
		      }
		
		      $crcword = self::intToDword(0); // unsigned long
		
		      for($i = 0; $i < 256; $i++){
		        $newbyte = self::intToDword(0); // unsigned long
		        
		        $x32term = self::intToDword(0); // unsigned long
		        
		        $j = 0; // int;
		
		        $crcword = self::intToDword(0);
		        
		        $newbyte = self::intToDword($i);
		        
		        for($j = 0; $j < 8; $j++){
		          $x32term = self::ulAnd(self::ulXor($crcword, $newbyte), self::intToDword(1));
		          
		          $crcword = self::ulXor(self::ulShiftR($crcword, 1),(ord($x32term[3]) == 1 ? self::intToDword(hex2int("EDB88320")) : self::intToDword(0)));
		          
		          $newbyte = self::ulShiftR($newbyte, 1);
		        }
		        
		        $crcTable[$i] = $crcword;
		      }
		      
		      fwrite($crc_file, serialize($crctable));
		      
		      fclose($crc_file);
		    } 
		    else{
		      $crc_file = fopen(SSH_CRC_CACHE_FILE,'r');
		      
		      $crcTable = unserialize(fread($crc_file, filesize(SSH_CRC_CACHE_FILE)));
		      
		      fclose($crc_file);
		    }
		  }
		  
		  return $crctable[$index];
		}
		
		public static function crc($buf){
		  $crcWord = self::intToDword(0); // unsigned long
		  
		  $p = 0; // pointer to the buf
		  
		  $len = strlen($buf); // len of sa buf
		  
		  while($len--){
		    $newbyte = $buf[$p++];
		    
		    $newbyte = self::ulXor($newbyte, self::ulAnd($crcWord, chr(0xFF)));
		    
		    $crcWord = self::ulXor(self::ulShiftR($crcWord, 8), self::crcTable(self::dwordToint($newbyte)));
		  }
		  return $crcWord;
		}
		
	  public static function mpIntRead($data, &$pos){
	    $bits_binary = substr($data, $pos, 2);
	    
	    $bits = ord($bits_binary[0]) * 256 + ord($bits_binary[1]);
	    
	    $pos += 2;
	  
	    $len = floor(($bits + 7) / 8);
	    
	    $result = "0";
	    
	    for($i = 0; $i < $len; $i++){
	      $result = bcmul($result,256);
	      
	      $result = bcadd($result,ord(substr($data,$pos,1))); $pos++;
	    }
	  
	    return $result;
	  }
	  
	  public static function mpIntCreate($int){
	    $out = self::intToblob($int);
	    
	    $bits = strlen($out)*8;
	    
	    $out = chr(($bits >> 8) & 255 ) . chr($bits & 255) . $out;
	    
	    return $out;
	  }
	  
	  public static function bitsSet($b){
	    $bits_set = 0;
	    
	    for($i = 0; $i < strlen($b); $i++){
	      $byte = decbin(ord($b[$i]));
	      
	      for($bit = 0; $bit < strlen($byte); $bit++){
	        if($byte[$bit] == 1){
	          $bits_set++;
	        }
	      }
	    }
	    
	    return $bits_set;
	  }
	  
	  public static function hexDump($s){
	    $line_pos = 0;
	    
	    for($i = 0; $i < strlen($s); $i++){
	      $line_pos++;
	      
	      echo str_pad(dechex(ord($s[$i])), 2, '0', STR_PAD_LEFT) . " ";
	      
	      if($line_pos % 8 == 0 && $line_pos % 16 != 0){
	        echo "- ";
	      }
	      
	      if($line_pos % 16 == 0){
	        echo "| " . strtr(substr($s, $line_pos-16, 16), "\r\n", "  ") . "\n";
	      }
	  
	      if(($i == strlen($s) - 1) && ($line_pos % 16 != 0)){
	        echo str_repeat(" ", (($line_pos % 16 <= 8) ? ((16 - ($line_pos % 16)) *3 + 2) : ((16 - ($line_pos % 16)) * 3))) . "| " . strtr(substr($s, $line_pos - ($line_pos % 16), 16), "\r\n","  ")."\n";
	      }
	    }
	    echo "\n";
	  }
	  
	  public static function hexDumpOpenSsh($s){
	    $line_pos = 0;
	    
	    for ($i = 0; $i < strlen($s); $i++){
	      $line_pos++;
	      
	      echo str_pad(dechex(ord($s[$i])), 2, '0', STR_PAD_LEFT) . ":";
	  
	      if($line_pos % 15 == 0){
	        echo "\n";
	      }
	    }
	    echo "\n";
	  }
  }